Circle
2021 - Sean Baxter - circlelang@gmail.com
Follow me on Twitter, @seanbax, for compiler updates.
Download the latest version: Build 139 for 64-bit Linux.
Circle is a new C++20 compiler. It's written from scratch and designed for easy extension.
The meta context
The most radical Circle feature is the meta context, which executes C++ code at compile time. Meta expression statements are evaluated at compile time. Meta for loops are unrolled at compile time.
#include <iostream>
int main() {
@meta std::cout<< "Hello world compile time!\n";
std::cout<< "Hello world runtime!\n";
}
$ circle hello.cxx
Hello world compile time!
$ ./hello
Hello world runtime!
Put the @meta
token at the start of a statement to execute it at compile time. When the statement is inside a template, it will be executed at template instantiation.
#include <iostream>
template<typename... Ts>
struct tuple {
// At instantiation, loop through each element in the parameter pack Ts.
// Subscript the i'th element Ts...[i].
// Declare a data member with name @(i): _0, _1, _2, etc.
@meta for(int i : sizeof...(Ts))
Ts...[i] @(i);
};
int main() {
using MyTuple = tuple<int, char, float>;
MyTuple my_tuple { 100, 'x', 3.14 };
// Use Circle reflection to access the member names of a class object.
// Use the pack slice operator ...[:] to convert an object into a pack.
// Print each member name and value with a pack expansion.
std::cout<< MyTuple.member_names<< ": "<< my_tuple...[:]<< "\n" ...;
}
$ circle tuple.cxx
$ ./tuple
_0: 100
_1: x
_2: 3.14
You can deploy meta control flow in any curly-brace scope: namespaces, enum-specifier, class-specifier, and function bodies. We can define a generic tuple type by looping over the template's parameter pack and depositing a data member for each element.
Major extensions
Circle extends C++ by adding many novel language features. Here are the big ones:
- Reflection and user-defined attributes
- Circle Imperative Arguments (CTA)
- Member traits
- Circle conditionals
- Universal member access
- Dynamic slices and list comprehensions
I've been programming C++ for 25 years. This is a compiler with the the capabilities I've always wanted.
The C++ of the future
Because Circle is so easy to extend, I've gone ahead and implemented some of the most promising WG21 proposals for the C++23 timeframe.
- P2392R1 - Pattern matching using
is
andas
- P1985R1 - Universal template parameters
- P0847R5 - Deducing this
- P2128R5 - Multidimensional subscript operator
- P0849R7 - auto(x): decay-copy in the language
- P2041R1 - template = delete
- P2025R2 - Guaranteed copy elision for return variables
- P2071R0 - Named Universal Character Escapes
- P1040R3 - std::embed
Taken together, these make C++ programming experience a lot nicer!
GPU targets
Circle's main target is 64-bit Linux. Because all modern non-Microsoft operating systems use the same C++ ABI, it would be easy to bring Circle to these other platforms.
But Circle supports both major GPU targets as well:
- Circle supports GPU compute with a single-pass flavor of CUDA.
- Use libc and the C++ Standard Library in device code.
- Use reflection and if-target to programmatically define the best tuning for each PTX architecture.
- Circle supports shader programming by embedding GLSL 4.6 into C++ and emitting both SPIR-V (OpenGL and Vulkan) and DXIL (Direct3D 12) byte codes.
- Tag functions and objects with
spirv
attributes to target all 12 standard shader stages. - Use reflection and user-defined attributes to automatically generate interfaces to guide shader development.
- Write device-portable GPGPU code with Vulkan compute, using real pointers into physical storage buffers, a powerful feature only implemented by the Circle compiler.
- Tag functions and objects with
Circle is a heterogeneous compiler. In one translation unit, mix x64 code, CUDA code, and shader code. No tags are required. Only a single frontend pass is performed. This is the most seamless integration of CPU and GPU code of any programming language.
Using Circle
Circle is best used locally, on any recent x86-64 Linux installation. You'll need a recent libstdc++, which should be provided by your distribution's package manager. I like libstdc++-10.2.
Download the latest version: Build 139 for 64-bit Linux.
If you're on an incompatible distribution, or a different OS altogether, try running Circle through this Dockerfile.
Circle is also hosted on Compiler Explorer, so you can use it over the web.
Basic command-line options
-
-S
emits an ASM file (.s). -
-S -emit-llvm
emits a human-readable LLVM IR file (.ll). -
-c -emit-llvm
emits a binary LLVM IR bitcode file (.bc). -
-c
only compiles to an object file, without linking. -
-console
prints the output (if text) to the terminal. This is very useful when paired with -S or -S -emit-llvm -
-O0
,-O1
,-O2
and-O3
are the usual optimization levels. -
-g
generates debug info. -
-M
names a compile-time shared object library. This enables compile-time foreign function calls to compiled code. -
-stat
reports on files opened and lines lexed. -
-no-demangle
prints mangled names in link errors. -
--gcc-toolchain
point Circle at an alternate libstdc++ distribution. This is part of a gcc tarball, such as gcc-10.2.0, which includes all the binaries and headers required to compile C++ programs. This command line argument sources the libstdc++ headers and precompiled static and dynamic objects, and ignores those installed by the distribution.